package com.onefish.toolkit.core;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.onefish.toolkit.entity.ThreadTemplate;
import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import sun.management.HotspotThreadMBean;
import sun.management.ManagementFactoryHelper;

import java.io.*;
import java.lang.management.*;
import java.nio.channels.FileChannel;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Component
public class ThreadHelper {
    private static final Logger logger = LoggerFactory.getLogger(ThreadHelper.class);
    public static ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    private static HotspotThreadMBean hotspotThreadMBean;
    //是否开启hotspot虚拟机线程监控和管理
    private static boolean enableHotspotThreadMBean = true;
    //上一次线程信息与cpu时间戳的映射关系
    private Map<ThreadTemplate, Long> lastCpuTimes = new HashMap<ThreadTemplate, Long>();
    //上一次抽样时间戳
    private long lastSampleTimeNanos;
    //是否包含内部线程
    private static boolean enableInternalThreads = true;
    private static Set<String> states = null;
    private long id = -1;
    public static final Integer topNLoad = 5;
    private boolean findMostBlockingThread = false;
    //采样间隙,默认200毫秒
    public static final int sampleInterval = 200;
    private String state;

    private boolean lockedMonitors = false;
    private boolean lockedSynchronizers = false;

    static {
        states = new HashSet<String>(Thread.State.values().length);
        for (Thread.State state : Thread.State.values()) {
            states.add(state.name());
        }
    }

    public ThreadGroup getRoot() {
        ThreadGroup group = Thread.currentThread().getThreadGroup();
        ThreadGroup parent;
        while ((parent = group.getParent()) != null) {
            group = parent;
        }
        return group;
    }

    public ThreadTemplate createThreadTemplate(Thread thread) {
        ThreadGroup group = thread.getThreadGroup();
        ThreadTemplate threadTemplate = new ThreadTemplate();
        threadTemplate.setId(thread.getId());
        threadTemplate.setName(thread.getName());
        threadTemplate.setGroup(group == null ? "" : group.getName());
        threadTemplate.setPriority(thread.getPriority());
        threadTemplate.setState(thread.getState());
        threadTemplate.setInterrupted(thread.isInterrupted());
        threadTemplate.setDaemon(thread.isDaemon());
        return threadTemplate;
    }

    public ThreadTemplate createThreadTemplate(String name) {
        ThreadTemplate threadTemplate = new ThreadTemplate();
        threadTemplate.setId(-1);
        threadTemplate.setName(name);
        threadTemplate.setPriority(-1);
        threadTemplate.setDaemon(true);
        threadTemplate.setInterrupted(false);
        return threadTemplate;
    }

    public List<ThreadTemplate> getThreads() {
        ThreadGroup root = getRoot();
        Thread[] threads = new Thread[root.activeCount()];
        while (root.enumerate(threads, true) == threads.length) {
            threads = new Thread[threads.length * 2];
        }
        List<ThreadTemplate> list = new ArrayList<ThreadTemplate>(threads.length);
        for (Thread thread : threads) {
            if (thread != null) {
                ThreadTemplate threadTemplate = createThreadTemplate(thread);
                list.add(threadTemplate);
            }
        }
        return list;
    }

    public Map<String, Long> getInternalThreadCpuTimes() {
        if (enableHotspotThreadMBean && enableInternalThreads) {
            try {
                if (hotspotThreadMBean == null) {
                    hotspotThreadMBean = ManagementFactoryHelper.getHotspotThreadMBean();
                }
                return hotspotThreadMBean.getInternalThreadCpuTimes();
            } catch (Throwable e) {
                //ignore ex
                enableHotspotThreadMBean = false;
            }
        }
        return null;
    }

    public ThreadInfo findThreadInfoById(ThreadInfo[] threadInfos, long id) {
        for (int i = 0; i < threadInfos.length; i++) {
            ThreadInfo threadInfo = threadInfos[i];
            if (threadInfo != null && threadInfo.getThreadId() == id) {
                return threadInfo;
            }
        }
        return null;
    }

    //抽样统计线程使用的cpu信息
    public List<ThreadTemplate> sample(Collection<ThreadTemplate> originThreads) {

        List<ThreadTemplate> threads = new ArrayList<ThreadTemplate>(originThreads);

        // 第一次抽样cpu
        if (lastCpuTimes.isEmpty()) {
            lastSampleTimeNanos = System.nanoTime();
            for (ThreadTemplate thread : threads) {
                if (thread.getId() > 0) {
                    //根据线程id获取当前线程的cpu时间(纳秒)
                    long cpu = threadMXBean.getThreadCpuTime(thread.getId());
                    lastCpuTimes.put(thread, cpu);
                    thread.setTime(cpu / 1000000);//毫秒
                }
            }

            //获取jvm内部所有线程的cpu时间
            Map<String, Long> internalThreadCpuTimes = getInternalThreadCpuTimes();
            if (internalThreadCpuTimes != null) {
                for (Map.Entry<String, Long> entry : internalThreadCpuTimes.entrySet()) {
                    String key = entry.getKey();
                    ThreadTemplate thread = createThreadTemplate(key);
                    thread.setTime(entry.getValue() / 1000000);
                    threads.add(thread);
                    lastCpuTimes.put(thread, entry.getValue());
                }
            }

            //按cpu时间降序排序
            Collections.sort(threads, (Comparator<ThreadTemplate>) (o1, o2) -> {
                long l1 = o1.getTime();
                long l2 = o2.getTime();
                if (l1 < l2) {
                    return 1;
                } else if (l1 > l2) {
                    return -1;
                } else {
                    return 0;
                }
            });
            return threads;
        }

        // 第二次抽样cpu
        long newSampleTimeNanos = System.nanoTime();
        Map<ThreadTemplate, Long> newCpuTimes = new HashMap<ThreadTemplate, Long>();
        for (ThreadTemplate thread : threads) {
            if (thread.getId() > 0) {
                long cpu = threadMXBean.getThreadCpuTime(thread.getId());
                newCpuTimes.put(thread, cpu);
            }
        }
        // jvm内部线程cpu时间与线程
        Map<String, Long> newInternalThreadCpuTimes = getInternalThreadCpuTimes();
        if (newInternalThreadCpuTimes != null) {
            for (Map.Entry<String, Long> entry : newInternalThreadCpuTimes.entrySet()) {
                ThreadTemplate threadTemplate = createThreadTemplate(entry.getKey());
                threads.add(threadTemplate);
                newCpuTimes.put(threadTemplate, entry.getValue());
            }
        }

        // 计算线程两次抽样的cpu增量时间
        final Map<ThreadTemplate, Long> deltas = new HashMap<ThreadTemplate, Long>();
        for (ThreadTemplate thread : newCpuTimes.keySet()) {
            Long t = lastCpuTimes.get(thread);
            if (t == null) {
                t = 0L;
            }
            long beforeTime = t;
            long afterTime = newCpuTimes.get(thread);
            //异常cpu时间
            if (beforeTime == -1) {
                beforeTime = afterTime;
            } else if (afterTime == -1) {
                afterTime = beforeTime;
            }
            //增量cpu时间
            long delta = afterTime - beforeTime;
            deltas.put(thread, delta);
        }
        //抽样时间间隔
        long sampleIntervalNanos = newSampleTimeNanos - lastSampleTimeNanos;
        //计算cpu使用率
        final HashMap<ThreadTemplate, Double> cpuUsages = new HashMap<ThreadTemplate, Double>();
        for (ThreadTemplate thread : threads) {
            double percent = (double) deltas.get(thread) / sampleIntervalNanos / 100;
            double cpuUsage = Double.parseDouble((NumberUtil.roundStr(percent, 4)));    //保留四位小数
            double cpu = sampleIntervalNanos == 0 ? 0 : cpuUsage;
            cpuUsages.put(thread, cpu);
        }

        //按线程cpu增量时间降序排序
        Collections.sort(threads, (o1, o2) -> {
            long l1 = deltas.get(o1);
            long l2 = deltas.get(o2);

            if (l1 < l2) {
                return 1;
            } else if (l1 > l2) {
                return -1;
            } else {
                return 0;
            }
        });
        for (ThreadTemplate thread : threads) {
            //统计的三个指标,线程cpu总时间、线程cpu增量时间、线程cpu使用率
            long timeMills = newCpuTimes.get(thread) / 1000000;
            long deltaTime = deltas.get(thread) / 1000000;
            double cpu = cpuUsages.get(thread);
            thread.setCpu(cpu);
            thread.setTime(timeMills);
            thread.setDeltaTime(deltaTime);
        }
        lastCpuTimes = newCpuTimes;
        lastSampleTimeNanos = newSampleTimeNanos;
        return threads;
    }

    public static String getFullStacktrace(ThreadInfo threadInfo, double cpuUsage, long deltaTime, long time, int lockIdentityHashCode,
                                           int blockingThreadCount) {
        StringBuilder sb = new StringBuilder("\"" + threadInfo.getThreadName() + "\"" + " Id="
                + threadInfo.getThreadId());

        if (cpuUsage >= 0 && cpuUsage <= 100) {
            sb.append(" cpuUsage=").append(cpuUsage).append("%");
        }
        if (deltaTime >= 0) {
            sb.append(" deltaTime=").append(deltaTime).append("ms");
        }
        if (time >= 0) {
            sb.append(" time=").append(time).append("ms");
        }

        sb.append(" ").append(threadInfo.getThreadState());

        if (threadInfo.getLockName() != null) {
            sb.append(" on ").append(threadInfo.getLockName());
        }
        if (threadInfo.getLockOwnerName() != null) {
            sb.append(" owned by \"").append(threadInfo.getLockOwnerName()).append("\" Id=").append(threadInfo.getLockOwnerId());
        }
        if (threadInfo.isSuspended()) {
            sb.append(" (suspended)");
        }
        if (threadInfo.isInNative()) {
            sb.append(" (in native)");
        }
        sb.append('\n');
        int i = 0;
        for (StackTraceElement ste : threadInfo.getStackTrace()) {
            sb.append("\tat ").append(ste.toString());
            sb.append('\n');
            if (i == 0 && threadInfo.getLockInfo() != null) {
                Thread.State ts = threadInfo.getThreadState();
                switch (ts) {
                    case BLOCKED:
                        sb.append("\t-  blocked on ").append(threadInfo.getLockInfo());
                        sb.append('\n');
                        break;
                    case WAITING:
                        sb.append("\t-  waiting on ").append(threadInfo.getLockInfo());
                        sb.append('\n');
                        break;
                    case TIMED_WAITING:
                        sb.append("\t-  waiting on ").append(threadInfo.getLockInfo());
                        sb.append('\n');
                        break;
                    default:
                }
            }

            for (MonitorInfo mi : threadInfo.getLockedMonitors()) {
                if (mi.getLockedStackDepth() == i) {
                    sb.append("\t-  locked ").append(mi);
                    if (mi.getIdentityHashCode() == lockIdentityHashCode) {
                        Ansi highlighted = Ansi.ansi().fg(Ansi.Color.RED);
                        highlighted.a(" <---- but blocks ").a(blockingThreadCount).a(" other threads!");
                        sb.append(highlighted.reset().toString());
                    }
                    sb.append('\n');
                }
            }
            ++i;
        }
        if (i < threadInfo.getStackTrace().length) {
            sb.append("\t...");
            sb.append('\n');
        }

        LockInfo[] locks = threadInfo.getLockedSynchronizers();
        if (locks.length > 0) {
            sb.append("\n\tNumber of locked synchronizers = ").append(locks.length);
            sb.append('\n');
            for (LockInfo li : locks) {
                sb.append("\t- ").append(li);
                if (li.getIdentityHashCode() == lockIdentityHashCode) {
                    sb.append(" <---- but blocks ").append(blockingThreadCount);
                    sb.append(" other threads!");
                }
                sb.append('\n');
            }
        }
        sb.append('\n');
        return sb.toString().replace("\t", "    ");
    }

    public void pause(long mills) {
        try {
            Thread.sleep(mills);
        } catch (Exception e) {
            throw new RuntimeException("线程休眠异常！", e);
        }
    }

    public static long[] toPrimitive(final Long[] array) {
        if (array == null) {
            return null;
        } else if (array.length == 0) {
            return new long[0];
        }
        final long[] result = new long[array.length];
        for (int i = 0; i < array.length; i++) {
            result[i] = array[i].longValue();
        }
        return result;
    }

    public static void main(String[] args) throws Exception {
//        String str = "123456";
//        Pattern pattern = Pattern.compile("[0-9]*");
//        boolean flag = pattern.matcher(str).matches();
//        System.out.println("flag = " + flag);
//        long num = (long) 2.1;
//        System.out.println("Math.rint(num) = " + Math.rint(num));
//        long num2 = (long) 2.5;
//        System.out.println("Math.rint(num2) = " + Math.rint(num2));
//        long num3 = (long) 3.6;
//        System.out.println("Math.rint(num3) = " + Math.rint(num3));
//        long num4 = (long) 3.5;
//        System.out.println("Math.rint(num4) = " + Math.rint(num4));
//        long before = System.nanoTime();
//        System.out.println("before = " + before);
//        Thread.sleep(1);
//        long after = System.nanoTime();
//        System.out.println("after = " + after);
//        System.out.println(after - before);
//        double a = 2.35345;
//        System.out.println("NumberUtil.round(a,2) = " + NumberUtil.round(a,2));
//        String jsonStr = "\n            name\tid\tage\n            aaa\t1\t20\n            bb\t2\t30\n        、    cccc\t3\t40";
//        System.out.println(jsonStr);
//        JSONObject jsonObject = new JSONObject();
//        jsonObject.put("content",jsonStr);
//        String json = JSON.toJSONString(jsonObject,true).replace("\\n","\n").replace("\\t","\t");
//        System.out.println(json);
//        JSONArray jsonArray = new JSONArray();
//        for (int i = 0; i < 3; i++) {
//            JSONObject object = new JSONObject();
//            object.put("content","\n            hello\n            world"+i);
//            jsonArray.add(object);
//        }
//        String json2 = JSON.toJSONString(jsonArray,false).replace("\\n","\n").replace("\\t","\t");
//        System.out.println(json2);
//        try {
//            int a = 2/0;
//        } catch (Exception e){
//            throw e;
//        }
        // 定义文件夹路径
        String folderPath = "C:\\Users\\Administrator\\Desktop\\test";
        printFileType(folderPath);


//        createzipFile("C:\\Users\\Administrator\\Desktop\\test","C:\\Users\\Administrator\\Desktop\\zip","test.zip");
//        String filePath = "C:\\Users\\Administrator\\Desktop\\";
//        String fileOrDirName = "test";
//        File sourceFile = new File(filePath + fileOrDirName);
//        String zipPath = "C:\\Users\\Administrator\\Desktop\\test.zip";
//        generateZipFile(zipPath, sourceFile);
        // 创建一个 File 对象，表示要压缩的文件或文件夹
//        File file = new File("C:\\Users\\Administrator\\Desktop\\test.zip");
//
//        // 创建一个 FileOutputStream 对象，用于写入压缩文件
//        FileOutputStream fileOutputStream = new FileOutputStream(file);
//
//        // 创建一个 ZipOutputStream 对象，用于将文件或文件夹写入压缩文件
//        ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
//
//        // 设置 ZipOutputStream 对象的压缩级别
//        zipOutputStream.setLevel(9);
//
//        // 设置 ZipOutputStream 对象的压缩算法
//        zipOutputStream.setMethod(ZipOutputStream.DEFLATED);
//
//        // 设置 ZipOutputStream 对象的压缩缓冲区大小
////        zipOutputStream.setBufferSize(1024);
//
//        // 设置 ZipOutputStream 对象的压缩文件名
//        zipOutputStream.putNextEntry(new ZipEntry("test.txt"));
//
//        // 设置 ZipOutputStream 对象的压缩文件内容
//        zipOutputStream.write("Hello World!".getBytes());
//
//        // 关闭 ZipOutputStream 对象
//        zipOutputStream.close();
    }
    public static void printFileType(String folderPath){
        try {
            // 创建文件夹路径的 Path 对象
            Path directoryPath = Paths.get(folderPath);
            // 使用 DirectoryStream 遍历文件夹中的文件和子文件夹
            DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directoryPath);
            for (Path path : directoryStream) {
                if (Files.isDirectory(path)) {
                    System.out.println("文件夹：" + path.toAbsolutePath() +  " 当前层级:" + path.getNameCount());
                    printFileType(path.toAbsolutePath().toString());
                } else {
                    System.out.println("文件：" + path.toAbsolutePath() +  " 当前层级:" + (path.getNameCount()-1));
                }
            }
            // 关闭 DirectoryStream
            directoryStream.close();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void generateZipFile(String zipPath, File sourceFile) {
        FileOutputStream fos = null;
        ZipOutputStream zos = null;
        //  校验压缩源文件是否有效
        if (Objects.isNull(sourceFile)) {
            return;
        }
        try {
            fos = new FileOutputStream(zipPath);
            zos = new ZipOutputStream(fos);
            // 设置压缩级别
            zos.setLevel(9);
            // 设置压缩算法
            zos.setMethod(ZipOutputStream.DEFLATED);
            //  如果是文件则直接压缩
            if (sourceFile.isFile()) {
                putFileEntry(zos, sourceFile, null);
            } else {
                //  如果是文件夹则需要递归压缩
                recursiveFolderZip(zos, sourceFile, null);
            }
            // 刷新数据到压缩文件中
            zos.flush();

        } catch (Exception e) {
            logger.error("generateZipFile error", e);
        } finally {
            //  关闭相关流
            try {
                if (!Objects.isNull(zos)) {
                    zos.closeEntry();
                    zos.close();
                }
                if (!Objects.isNull(fos)) {
                    fos.close();
                }
            } catch (Exception E) {
                logger.error("stream close error", E);
            }
        }
    }
    /**
     * 递归压缩文件夹
     *
     * @param zos    压缩流
     * @param folder 压缩的文件夹
     * @param dir    压缩的当前目录
     */
    public static void recursiveFolderZip(ZipOutputStream zos, File folder, String dir) {
        File[] listFiles = folder.listFiles();
        //  文件夹下不存在文件则直接退出
        if (listFiles == null || listFiles.length == 0) {
            return;
        }
        for (File file : listFiles) {
            //  如果是文件则直接压缩
            if (file.isFile()) {
                putFileEntry(zos, file, dir);
            }
            //  如果是目录则递归遍历并生成压缩目录
            if (file.isDirectory()) {
                if (Objects.isNull(dir)) {
                    dir = file.getName();
                } else {
                    dir = dir + "\\" + file.getName();
                }
                recursiveFolderZip(zos, file, dir);
                // 退出当次递归，返回到上级目录
                dir = obtainParentDir(dir);
            }
        }
    }

    /**
     * 压缩文件
     * @param zos  压缩流
     * @param file 待压缩的文件
     * @param dir  当前目录
     */
    public static void putFileEntry(ZipOutputStream zos, File file, String dir) {
        FileInputStream fis = null;
        try {
            String fileName = file.getName();
            //  如果最初传进来的是根目录（相对路径），那么不处理，否则就要在当前文件名的基础上加上父级的文件夹名
            if (!Objects.isNull(dir)) {
                fileName = dir + "\\" + fileName;
            }
            //  当前文件是目录的话，将文件夹先压缩进去
            if (file.isDirectory()) {
                ZipEntry ze = new ZipEntry(fileName + "\\");
                zos.putNextEntry(ze);
                return;
            }
            //  将文件内容设置到压缩流中
            fis = new FileInputStream(file);
            ZipEntry ze = new ZipEntry(fileName);
            zos.putNextEntry(ze);
            //  缓存块大小为1mb
            byte[] buffer = new byte[1024 * 1024];
            int len;
            while ((len = fis.read(buffer)) > 0) {
                zos.write(buffer, 0, len);
            }
        } catch (Exception e) {
            logger.error("putFileEntry error", e);
        } finally {
            //  关闭当前文件流
            try {
                if (!Objects.isNull(fis)) {
                    fis.close();
                }
            } catch (Exception E) {
                logger.error("FileInputStream close error", E);
            }
        }
    }

    /**
     * 获取当前目录的父目录路径
     * @param dir 当前目录
     * @return 父目录
     */
    public static String obtainParentDir(String dir) {
        if (Objects.isNull(dir)) {
            return null;
        }
        String[] split = dir.split("\\\\");
        if (split.length <= 1) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < split.length - 1; i++) {
            sb.append(split[i]).append("\\");
        }
        return sb.substring(0, sb.length() - 1);// -1 是去除目录分隔符\
    }

    public static void createzipFile(String source, String zipPath, String zipName) {
        File sourceFile = new File(source);
        try {
            File zipPathFile = new File(zipPath);
            if (!zipPathFile.exists()) {
                zipPathFile.mkdirs();
            }
            FileOutputStream target = new FileOutputStream(zipName);
            ZipOutputStream out = new ZipOutputStream(target);
            // 设置压缩级别
            out.setLevel(9);
            // 设置压缩算法
            out.setMethod(ZipOutputStream.DEFLATED);
            getZipFile(out, sourceFile, "");
            out.flush();
            out.close();
        } catch (Exception e) {
//            e.printStackTrace();
            logger.error("createzipFile error", e);
        }
    }

    public static void getZipFile(ZipOutputStream out, File file, String pareFileName) {
        try {
            File files[] = file.listFiles();
            for (File dirFile : files) {
                String temPath = pareFileName;
                if (dirFile.isDirectory()) {
                    pareFileName += dirFile.getName() + File.separator;
                    ZipEntry zipEntry = new ZipEntry(pareFileName);
                    out.putNextEntry(zipEntry);
                    getZipFile(out, dirFile, pareFileName);
                } else {
                    pareFileName += dirFile.getName();
                    FileInputStream fi = new FileInputStream(dirFile);
                    BufferedInputStream origin = new BufferedInputStream(fi);
                    ZipEntry entry = new ZipEntry(pareFileName);
                    out.putNextEntry(entry);
                    int count;
                    while ((count = origin.read()) != -1) {
                        out.write(count);
                    }
                    origin.close();
                }
                pareFileName = temPath;
            }
        } catch (Exception e) {
//            e.printStackTrace();
            logger.error("getZipFile error", e);
        }
    }
}
